﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BeanboxSoftware.BeanMap
{
  public partial class Map<K1, K2, V> : Internal.MapBase<Func<K1, K2, V>, V, K1, IDictionary<K2, V>>, IMap<K1, K2, V>
  {
    public Map() { }

    public Map(Map<K1, K2, V> copy)
    {
      foreach (var kv in copy.KeyValues)
      {
        this[kv.Key.Item1, kv.Key.Item2] = kv.Value;
      }
    }

    public Map(IEnumerable<V> list, Func<V, K1> key1Gen, Func<V, K2> key2Gen)
    {
      foreach (var item in list)
      {
        this[key1Gen(item), key2Gen(item)] = item;
      }
    }

    protected IDictionary<K2, V> Prepare(K1 k1)
    {
      var data = SafeGet();

      if (!data.ContainsKey(k1))
      {
        data[k1] = new Dictionary<K2, V>();
      }

      return data[k1];
    }

    public IMap<K2, V> this[K1 k1]
    {
      get
      {
        return new Internal.MapBase<Func<K2, V>, V, K2, V>._PassthroughFactory().CreatePassthrough<Map<K2, V>>(
          build => ContainsKey(k1) ? RetrieveDataHandle()[k1] : build ? Prepare(k1) : null,
          () => RemovalCleanup(k1),
          k2 => this[k1, k2]);
      }
    }

    protected void SafeSet(K1 k1, K2 k2, V value)
    {
      var data = Prepare(k1);
      data[k2] = value;
    }

    public V this[K1 k1, K2 k2]
    {
      get
      {
        if (!ContainsKey(k1, k2))
        {
          return HandleKeyNotFound(gen => gen(k1, k2), v => this[k1, k2] = v, () => new object[] { k1, k2 });
        }

        return RetrieveDataHandle()[k1][k2];
      }
      set
      {
        SafeSet(k1, k2, value);
      }
    }

    public bool ContainsKey(K1 k1, K2 k2)
    {
      return
        RetrieveDataHandle().ContainsKey(k1) &&
        RetrieveDataHandle()[k1].ContainsKey(k2);
    }

    public override int Count
    {
      get { return RetrieveDataHandle().Sum(e => e.Value.Count); }
    }

    public override bool TryGetValue(K1 key, out IDictionary<K2, V> value)
    {
      value = this[key];
      return true;
    }

    protected override IDictionary<K2, V> IndexerGet(K1 key)
    {
      return this[key];
    }

    public IEnumerator<KeyValuePair<Tuple<K1, K2>, V>> GetEnumerator()
    {
      return KeyValues.GetEnumerator();
    }

    public IEnumerable<KeyValuePair<Tuple<K1, K2>, V>> KeyValues
    {
      get
      {
        foreach (var k1 in RetrieveDataHandle().Keys)
        {
          foreach (var k2 in RetrieveDataHandle()[k1].Keys)
          {
            yield return new KeyValuePair<Tuple<K1, K2>, V>(Tuple.Create(k1, k2), RetrieveDataHandle()[k1][k2]);
          }
        }
      }
    }

    public IEnumerable<Tuple<K1, K2>> Keys
    {
      get
      {
        return KeyValues.Select(kvs => kvs.Key);
      }
    }

    public IEnumerable<V> Values
    {
      get
      {
        return KeyValues.Select(kvs => kvs.Value);
      }
    }

    public bool Remove(K1 k1, K2 k2)
    {
      var data = RetrieveDataHandle();

      if (!data.ContainsKey(k1))
      {
        return false;
      }

      if (data[k1].Remove(k2))
      {
        RemovalCleanup(k1);
        return true;
      }

      return false;
    }

    protected void RemovalCleanup(K1 k1)
    {
      var data = RetrieveDataHandle();
      if (data.ContainsKey(k1) && data[k1].Count == 0)
      {
        data.Remove(k1);
      }

      RemovalCleanup();
    }

    public V Get(Tuple<K1, K2> key)
    {
      return this[key.Item1, key.Item2];
    }

    public V Get(K1 k1, K2 k2)
    {
      return this[k1, k2];
    }

    public IMap<K2, V> Get(K1 key)
    {
      return this[key];
    }

    public bool ContainsKey(Tuple<K1, K2> key)
    {
      return ContainsKey(key.Item1, key.Item2);
    }
  }
}
